#include "stdafx.h"
#include "stdlib/datetimeparser.hpp"
#include "charset/ascii.hpp"
using namespace JHCPP::charset;
#include "stdlib/exception.hpp"
#include "stdlib/datetime.hpp"
#include "stdlib/datetimeformat.hpp"
using namespace JHCPP::stdlib::jhException;

namespace JHCPP
{
	namespace stdlib
	{
	#define SKIP_JUNK() \
	while (it != end && !CAscii::isDigit(*it)) ++it

	#define SKIP_DIGITS() \
	while (it != end && CAscii::isDigit(*it)) ++it

	#define PARSE_NUMBER(var) \
	while (it != end && CAscii::isDigit(*it)) var = var*10 + ((*it++) - '0')

	#define PARSE_NUMBER_N(var, n) \
		{ int i = 0; while (i++ < n && it != end && CAscii::isDigit(*it)) var = var*10 + ((*it++) - '0'); }

	#define PARSE_FRACTIONAL_N(var, n) \
		{ int i = 0; while (i < n && it != end && CAscii::isDigit(*it)) { var = var*10 + ((*it++) - '0'); i++; } while (i++ < n) var *= 10; }


		void CDateTimeParser::parse(const std::string& fmt, const std::string& str, CDateTime& dateTime, int& timeZoneDifferential)
		{
			int year   = 0;
			int month  = 0;
			int day    = 0;
			int hour   = 0;
			int minute = 0;
			int second = 0;
			int millis = 0;
			int micros = 0;
			int tzd    = 0;

			std::string::const_iterator it   = str.begin();
			std::string::const_iterator end  = str.end();
			std::string::const_iterator itf  = fmt.begin();
			std::string::const_iterator endf = fmt.end();

			while (itf != endf && it != end)
			{
				if (*itf == '%')
				{
					if (++itf != endf)
					{
						switch (*itf)
						{
						case 'w':
						case 'W':
							while (it != end && CAscii::isSpace(*it)) ++it;
							while (it != end && CAscii::isAlpha(*it)) ++it;
							break;
						case 'b':
						case 'B':
							month = parseMonth(it, end);
							break;
						case 'd':
						case 'e':
						case 'f':
							SKIP_JUNK();
							PARSE_NUMBER_N(day, 2);
							break;
						case 'm':
						case 'n':
						case 'o':
							SKIP_JUNK();
							PARSE_NUMBER_N(month, 2);
							break;					 
						case 'y':
							SKIP_JUNK();
							PARSE_NUMBER_N(year, 2);
							if (year >= 69) 
								year += 1900;
							else
								year += 2000;
							break;
						case 'Y':
							SKIP_JUNK();
							PARSE_NUMBER_N(year, 4);
							break;
						case 'r':
							SKIP_JUNK();
							PARSE_NUMBER(year);
							if (year < 1000)
							{
								if (year >= 69) 
									year += 1900;
								else
									year += 2000;
							}
							break;
						case 'H':
						case 'h':
							SKIP_JUNK();
							PARSE_NUMBER_N(hour, 2);
							break;
						case 'a':
						case 'A':
							hour = parseAMPM(it, end, hour);
							break;
						case 'M':
							SKIP_JUNK();
							PARSE_NUMBER_N(minute, 2);
							break;
						case 'S':
							SKIP_JUNK();
							PARSE_NUMBER_N(second, 2);
							break;
						case 's':
							SKIP_JUNK();
							PARSE_NUMBER_N(second, 2);
							if (it != end && (*it == '.' || *it == ','))
							{
								++it;
								PARSE_FRACTIONAL_N(millis, 3);
								PARSE_FRACTIONAL_N(micros, 3);
								SKIP_DIGITS();
							}
							break;
						case 'i':
							SKIP_JUNK();
							PARSE_NUMBER_N(millis, 3);
							break;
						case 'c':
							SKIP_JUNK();
							PARSE_NUMBER_N(millis, 1);
							millis *= 100;
							break;
						case 'F':
							SKIP_JUNK();
							PARSE_FRACTIONAL_N(millis, 3);
							PARSE_FRACTIONAL_N(micros, 3);
							SKIP_DIGITS();
							break;
						case 'z':
						case 'Z':
							tzd = parseTZD(it, end);
							break;
						}
						++itf;
					}
				}
				else ++itf;
			}
			if (month == 0) month = 1;
			if (day == 0) day = 1;
			if (CDateTime::isValid(year, month, day, hour, minute, second, millis, micros))
				dateTime.assign(year, month, day, hour, minute, second, millis, micros);
			else 
				throw SyntaxException("date/time component out of range");
			timeZoneDifferential = tzd;
		}

		CDateTime CDateTimeParser::parse(const std::string& fmt, const std::string& str, int& timeZoneDifferential)
		{
			CDateTime result;
			parse(fmt, str, result, timeZoneDifferential);
			return result;
		}

		bool CDateTimeParser::tryParse(const std::string& fmt, const std::string& str, CDateTime& dateTime, int& timeZoneDifferential)
		{
			try
			{
				parse(fmt, str, dateTime, timeZoneDifferential);
			}
			catch (CException&)
			{
				return false;
			}
			return true;
		}

		void CDateTimeParser::parse(const std::string& str, CDateTime& dateTime, int& timeZoneDifferential)
		{
			if (!tryParse(str, dateTime, timeZoneDifferential))
				throw SyntaxException("Unsupported or invalid date/time format");
		}

		CDateTime CDateTimeParser::parse(const std::string& str, int& timeZoneDifferential)
		{
			CDateTime result;
			if (tryParse(str, result, timeZoneDifferential))
				return result;
			else
				throw SyntaxException("Unsupported or invalid date/time format");
		}

		bool CDateTimeParser::tryParse(const std::string& str, CDateTime& dateTime, int& timeZoneDifferential)
		{
			if (str.length() < 4) return false;

			if (str[3] == ',')
				return tryParse("%w, %e %b %r %H:%M:%S %Z", str, dateTime, timeZoneDifferential);
			else if (str[3] == ' ')
				return tryParse(CDateTimeFormat::ASCTIME_FORMAT, str, dateTime, timeZoneDifferential);
			else if (str.find(',') < 10)
				return tryParse("%W, %e %b %r %H:%M:%S %Z", str, dateTime, timeZoneDifferential);
			else if (CAscii::isDigit(str[0]))
			{
				if (str.find(' ') != std::string::npos || str.length() == 10)
					return tryParse(CDateTimeFormat::SORTABLE_FORMAT, str, dateTime, timeZoneDifferential);
				else if (str.find('.') != std::string::npos || str.find(',') != std::string::npos)
					return tryParse(CDateTimeFormat::ISO8601_FRAC_FORMAT, str, dateTime, timeZoneDifferential);
				else
					return tryParse(CDateTimeFormat::ISO8601_FORMAT, str, dateTime, timeZoneDifferential);
			}
			else return false;
		}

		int CDateTimeParser::parseTZD(std::string::const_iterator& it, const std::string::const_iterator& end)
		{
			struct Zone
			{
				const char* designator;
				int         timeZoneDifferential;
			};

			static Zone zones[] =
			{
				{"Z",           0},
				{"UT",          0},
				{"GMT",         0},
				{"BST",    1*3600},
				{"IST",    1*3600},
				{"WET",         0},
				{"WEST",   1*3600},
				{"CET",    1*3600},
				{"CEST",   2*3600},
				{"EET",    2*3600},
				{"EEST",   3*3600},
				{"MSK",    3*3600},
				{"MSD",    4*3600},
				{"NST",   -3*3600-1800},
				{"NDT",   -2*3600-1800},
				{"AST",   -4*3600},
				{"ADT",   -3*3600},
				{"EST",   -5*3600},
				{"EDT",   -4*3600},
				{"CST",   -6*3600},
				{"CDT",   -5*3600},
				{"MST",   -7*3600},
				{"MDT",   -6*3600},
				{"PST",   -8*3600},
				{"PDT",   -7*3600},
				{"AKST",  -9*3600},
				{"AKDT",  -8*3600},
				{"HST",  -10*3600},
				{"AEST",  10*3600},
				{"AEDT",  11*3600},
				{"ACST",   9*3600+1800},
				{"ACDT",  10*3600+1800},
				{"AWST",   8*3600},
				{"AWDT",   9*3600}
			};

			int tzd = 0;
			while (it != end && CAscii::isSpace(*it)) ++it;
			if (it != end)
			{
				if (CAscii::isAlpha(*it))
				{
					std::string designator;
					designator += *it++;
					if (it != end && CAscii::isAlpha(*it)) designator += *it++;
					if (it != end && CAscii::isAlpha(*it)) designator += *it++;
					if (it != end && CAscii::isAlpha(*it)) designator += *it++;
					for (unsigned i = 0; i < sizeof(zones)/sizeof(Zone); ++i)
					{
						if (designator == zones[i].designator)
						{
							tzd = zones[i].timeZoneDifferential;
							break;
						}
					}
				}
				if (it != end && (*it == '+' || *it == '-'))
				{
					int sign = *it == '+' ? 1 : -1;
					++it;
					int hours = 0;
					PARSE_NUMBER_N(hours, 2);
					if (it != end && *it == ':') ++it;
					int minutes = 0;
					PARSE_NUMBER_N(minutes, 2);
					tzd += sign*(hours*3600 + minutes*60);
				}
			}
			return tzd;
		}

		int CDateTimeParser::parseMonth(std::string::const_iterator& it, const std::string::const_iterator& end)
		{
			std::string month;
			while (it != end && (CAscii::isSpace(*it) || CAscii::isPunct(*it))) ++it;
			bool isFirst = true;
			while (it != end && CAscii::isAlpha(*it)) 
			{
				char ch = (*it++);
				if (isFirst) { month += CAscii::toUpper(ch); isFirst = false; }
				else month += CAscii::toLower(ch);
			}
			if (month.length() < 3) throw SyntaxException("Month name must be at least three characters long", month);
			for (int i = 0; i < 12; ++i) 
			{
				if (CDateTimeFormat::MONTH_NAMES[i].find(month) == 0)
					return i + 1;
			}
			throw SyntaxException("Not a valid month name", month);
		}

		int CDateTimeParser::parseDayOfWeek(std::string::const_iterator& it, const std::string::const_iterator& end)
		{
			std::string dow;
			while (it != end && (CAscii::isSpace(*it) || CAscii::isPunct(*it))) ++it;
			bool isFirst = true;
			while (it != end && CAscii::isAlpha(*it)) 
			{
				char ch = (*it++);
				if (isFirst) { dow += CAscii::toUpper(ch); isFirst = false; }
				else dow += CAscii::toLower(ch);
			}
			if (dow.length() < 3) throw SyntaxException("Weekday name must be at least three characters long", dow);
			for (int i = 0; i < 7; ++i) 
			{
				if (CDateTimeFormat::WEEKDAY_NAMES[i].find(dow) == 0)
					return i;
			}
			throw SyntaxException("Not a valid weekday name", dow);
		}

		int CDateTimeParser::parseAMPM(std::string::const_iterator& it, const std::string::const_iterator& end, int hour)
		{
			std::string ampm;
			while (it != end && (CAscii::isSpace(*it) || CAscii::isPunct(*it))) ++it;
			while (it != end && CAscii::isAlpha(*it)) 
			{
				char ch = (*it++);
				ampm += CAscii::toUpper(ch);
			}
			if (ampm == "AM")
			{
				if (hour == 12)
					return 0;
				else
					return hour;
			}
			else if (ampm == "PM")
			{
				if (hour < 12)
					return hour + 12;
				else
					return hour;
			}
			else throw SyntaxException("Not a valid AM/PM designator", ampm);
		}
	}//end of namespace stdlib
}//end of namespace JHCPP
